﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;

namespace ToddMcDermid.SSIS
{
    #region CLASS: ColumnConversionConfig
    public class ColumnConversionConfig : IXmlSerializable
    {
        #region Constants
        public const string STRINGFORMAT_DATE = "yyyy-MM-dd";
        public const string STRINGFORMAT_DATETIME = "yyyy-MM-ddThh:mm:ss.fff";
        #endregion

        #region Public Enumerations
        public enum ConversionValidationResultType
        {
            Valid,
            Warning,
            Error
        }
        #endregion

        #region Private Variables
        private string _columnName;
        private int _inLineageID;
        private DataType _inDataType;
        private int _inLength;
        private int _inCodePage;
        private int _inPrecision;
        private int _inScale;
        private bool _wantToOutput;
        private bool _wantToConvert;
        private int _outLineageID;
        private DataType _outDataType;
        private int _outLength;
        private int _outCodePage;
        private int _outPrecision;
        private int _outScale;
        private int _errorLineageID;
        private DTSRowDisposition _errorRowDisposition;
        private DTSRowDisposition _truncationRowDisposition;
        private ConversionValidationResultType _conversionValidationResult;
        private string _conversionValidationMessage;
        private bool _iconsLoaded;
        private Bitmap _iconNotApplicable;
        private Bitmap _iconWarning;
        private Bitmap _iconError;
        private Bitmap _iconValid;

        private int _runtimeInBufferIndex;
        private int _runtimeOutBufferIndex;
        private int _runtimeErrorBufferIndex;
        #endregion

        #region Constructor
        public ColumnConversionConfig()
        {
            this._wantToOutput = true;
            this._wantToConvert = false;
            this._errorRowDisposition = DTSRowDisposition.RD_FailComponent;
            this._truncationRowDisposition = DTSRowDisposition.RD_FailComponent;
            this._conversionValidationResult = ConversionValidationResultType.Valid;
        }
        #endregion

        #region Properties
        public string ColumnName
        {
            get { return this._columnName; }
        }

        public int InLineageID
        {
            get { return this._inLineageID; }
        }

        public DataType InDataType
        {
            get { return this._inDataType; }
        }

        public int InLength
        {
            get { return this._inLength; }
        }

        public int InCodePage
        {
            get { return this._inCodePage; }
        }

        public int InPrecision
        {
            get { return this._inPrecision; }
        }

        public int InScale
        {
            get { return this._inScale; }
        }

        public bool WantToOutput
        {
            get { return this._wantToOutput; }
            set
            {
                if (value)
                {
                    this.StartConversion();
                    this._wantToOutput = true;
                }
                else
                {
                    this.ClearConversion();
                    this._wantToOutput = false;
                }
            }
        }

        public bool WantToConvert
        {
            get { return this._wantToConvert; }
            set 
            {
                if (value)
                {
                    this.StartConversion();
                }
                else
                {
                    this.ClearConversion();
                }
            }
        }

        public bool Converts
        {
            get 
            {
                if (!this._wantToConvert)
                {
                    return false;
                }
                else
                {
                    return ((this._inDataType != this._outDataType)
                        || (this._inLength != this._outLength)
                        || (this._inCodePage != this._outCodePage)
                        || (this._inPrecision != this._outPrecision)
                        || (this._inScale != this._outScale));
                }
            }
        }

        public int OutLineageID
        {
            get { return this._outLineageID; }
            set { this._outLineageID = value; }
        }

        public DataType OutDataType
        {
            get
            {
                if (this._wantToConvert)
                {
                    return this._outDataType;
                }
                else
                {
                    return this._inDataType;
                }
            }
            set 
            {
                this.StartConversion();
                this._outDataType = value;
                this._outLength = 0;
                this._outCodePage = 0;
                this._outPrecision = 0;
                this._outScale = 0;
                switch (this._outDataType)
                {
                    case DataType.DT_STR:
                        #region Set code page and length defaults
                        this._outLength = Math.Max(1, this._inLength);
                        this._outCodePage = this._inCodePage;
                        if (this._outCodePage == 0)
                        {
                            this._outCodePage = 1252;
                        }
                        switch (this._inDataType)
                        {
                            case DataType.DT_BOOL:
                                this._outLength = Math.Max(5, this._outLength);
                                break;

                            case DataType.DT_DBDATE:
                                this._outLength = Math.Max(10, this._outLength);
                                break;

                            case DataType.DT_DATE:
                            case DataType.DT_DBTIMESTAMP:
                                this._outLength = Math.Max(24, this._outLength);
                                break;

                            case DataType.DT_I1:
                                this._outLength = Math.Max(4, this._outLength);
                                break;

                            case DataType.DT_I2:
                                this._outLength = Math.Max(6, this._outLength);
                                break;

                            case DataType.DT_I4:
                                this._outLength = Math.Max(11, this._outLength);
                                break;

                            case DataType.DT_I8:
                                this._outLength = Math.Max(21, this._outLength);
                                break;

                            case DataType.DT_UI1:
                                this._outLength = Math.Max(3, this._outLength);
                                break;

                            case DataType.DT_UI2:
                                this._outLength = Math.Max(5, this._outLength);
                                break;

                            case DataType.DT_UI4:
                                this._outLength = Math.Max(10, this._outLength);
                                break;

                            case DataType.DT_UI8:
                                this._outLength = Math.Max(20, this._outLength);
                                break;
                        }
                        break;
                        #endregion

                    case DataType.DT_WSTR:
                        #region Set length default
                        this._outLength = Math.Max(1, this._inLength);
                        switch (this._inDataType)
                        {
                            case DataType.DT_BOOL:
                                this._outLength = Math.Max(5, this._outLength);
                                break;

                            case DataType.DT_DBDATE:
                                this._outLength = Math.Max(10, this._outLength);
                                break;

                            case DataType.DT_DATE:
                            case DataType.DT_DBTIMESTAMP:
                                this._outLength = Math.Max(24, this._outLength);
                                break;

                            case DataType.DT_I1:
                                this._outLength = Math.Max(4, this._outLength);
                                break;

                            case DataType.DT_I2:
                                this._outLength = Math.Max(6, this._outLength);
                                break;

                            case DataType.DT_I4:
                                this._outLength = Math.Max(11, this._outLength);
                                break;

                            case DataType.DT_I8:
                                this._outLength = Math.Max(21, this._outLength);
                                break;

                            case DataType.DT_UI1:
                                this._outLength = Math.Max(3, this._outLength);
                                break;

                            case DataType.DT_UI2:
                                this._outLength = Math.Max(5, this._outLength);
                                break;

                            case DataType.DT_UI4:
                                this._outLength = Math.Max(10, this._outLength);
                                break;

                            case DataType.DT_UI8:
                                this._outLength = Math.Max(20, this._outLength);
                                break;
                        }
                        break;
                        #endregion
                }
                this.Validate();
            }
        }

        public int OutLength
        {
            get
            {
                if (this._wantToConvert)
                {
                    return this._outLength;
                }
                else
                {
                    return this._inLength;
                }
            }
            set 
            {
                this.StartConversion();
                this._outLength = value;
                this.Validate();
            }
        }

        public int OutCodePage
        {
            get
            {
                if (this._wantToConvert)
                {
                    return this._outCodePage;
                }
                else
                {
                    return this._inCodePage;
                }
            }
            set 
            {
                this.StartConversion();
                this._outCodePage = value;
                this.Validate();
            }
        }

        public int OutPrecision
        {
            get
            {
                if (this._wantToConvert)
                {
                    return this._outPrecision;
                }
                else
                {
                    return this._inPrecision;
                }
            }
            set 
            {
                this.StartConversion();
                this._outPrecision = value;
                this.Validate();
            }
        }

        public int OutScale
        {
            get
            {
                if (this._wantToConvert)
                {
                    return this._outScale;
                }
                else
                {
                    return this._inScale;
                }
            }
            set
            {
                this.StartConversion();
                this._outScale = value;
                this.Validate();
            }
        }

        public int ErrorLineageID
        {
            get { return this._errorLineageID; }
            set { this._errorLineageID = value; }
        }

        public DTSRowDisposition ErrorRowDisposition
        {
            get { return this._errorRowDisposition; }
            set 
            { 
                this._errorRowDisposition = value;
                this.Validate();
            }
        }

        public DTSRowDisposition TruncationRowDisposition
        {
            get { return this._truncationRowDisposition; }
            set 
            { 
                this._truncationRowDisposition = value;
                this.Validate();
            }
        }

        public ConversionValidationResultType ConversionValidationResult
        {
            get { return this._conversionValidationResult; }
        }

        public string ConversionValidationMessage
        {
            get 
            {
                if (this._conversionValidationResult == ConversionValidationResultType.Valid)
                {
                    return "Valid";
                }
                else
                {
                    return Enum.GetName(typeof(ConversionValidationResultType), this._conversionValidationResult) + " - " + this._conversionValidationMessage;
                }
            }
        }

        public Bitmap ConversionValidationIcon
        {
            get
            {
                if (!this._iconsLoaded)
                {
                    this._iconNotApplicable = new Icon(this.GetType(), "ConversionNotApplicable.ico").ToBitmap();
                    this._iconWarning = new Icon(this.GetType(), "ConversionWarning.ico").ToBitmap();
                    this._iconError = new Icon(this.GetType(), "ConversionError.ico").ToBitmap();
                    this._iconValid = new Icon(this.GetType(), "ConversionValid.ico").ToBitmap();
                    this._iconsLoaded = true;
                }
                if (this.WantToConvert)
                {
                    switch (this._conversionValidationResult)
                    {
                        case ConversionValidationResultType.Valid:
                            return this._iconValid;
                        case ConversionValidationResultType.Warning:
                            return this._iconWarning;
                        default:
                            return this._iconError;
                    }
                }
                else
                {
                    return this._iconNotApplicable;
                }
            }
        }

        public int RuntimeInBufferIndex
        {
            get { return this._runtimeInBufferIndex; }
            set { this._runtimeInBufferIndex = value; }
        }

        public int RuntimeOutBufferIndex
        {
            get { return this._runtimeOutBufferIndex; }
            set { this._runtimeOutBufferIndex = value; }
        }

        public int RuntimeErrorBufferIndex
        {
            get { return this._runtimeErrorBufferIndex; }
            set { this._runtimeErrorBufferIndex = value; }
        }
        #endregion

        #region Property Assignment Methods
        public void SetInput(int inLineageID, string inColumnName)
        {
            this._inLineageID = inLineageID;
            this._columnName = inColumnName;
        }

        public void SetInputDataType(DataType inDataType, int inLength, int inCodePage, int inPrecision, int inScale)
        {
            this._inDataType = inDataType;
            this._inLength = inLength;
            this._inCodePage = inCodePage;
            this._inPrecision = inPrecision;
            this._inScale = inScale;
        }

        public void ClearConversion()
        {
            this._wantToConvert = false;
        }

        public void StartConversion()
        {
            if (!this._wantToConvert)
            {
                this._wantToOutput = true;
                this._wantToConvert = true;
                this._outDataType = this._inDataType;
                this._outLength = this._inLength;
                this._outCodePage = this._inCodePage;
                this._outPrecision = this._inPrecision;
                this._outScale = this._inScale;
            }
        }
        #endregion

        #region Validate
        private void Validate()
        {
            this._conversionValidationResult = ConversionValidationResultType.Valid;
            this._conversionValidationMessage = null;

            if (this._wantToConvert)
            {
                int maxInDigits = 0;
                int maxInBits = 0;
                int maxOutDigits = 0;
                int maxOutBits = 0;

                #region Validate Conversion
                switch (this._inDataType)
                {
                    case DataType.DT_BOOL:
                        #region Boolean
                        switch (this._outDataType)
                        {
                            #region No conversion errors possible
                            case DataType.DT_BOOL:
                            case DataType.DT_CY:
                            case DataType.DT_DECIMAL:
                            case DataType.DT_FILETIME:
                            case DataType.DT_NUMERIC:
                            case DataType.DT_I1:
                            case DataType.DT_UI1:
                            case DataType.DT_I2:
                            case DataType.DT_I4:
                            case DataType.DT_I8:
                            case DataType.DT_R4:
                            case DataType.DT_R8:
                            case DataType.DT_UI2:
                            case DataType.DT_UI4:
                            case DataType.DT_UI8:
                                // no conversion errors possible
                                break;
                            #endregion

                            #region Warn about possible string truncation
                            case DataType.DT_STR:
                            case DataType.DT_WSTR:
                                if ((this._outLength < 5) && (this._truncationRowDisposition != DTSRowDisposition.RD_IgnoreFailure))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = this._columnName + " only has " + this._outLength.ToString() + " characters defined "
                                        + "for converting a " + DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName + " to a "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName + ".  "
                                        + "This conversion requires 5 characters (to allow conversion into 'True' and 'False', and may result in truncation.  "
                                        + "Either increase the size of the converted " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + ", or set the Truncation Behaviour to Ignore.";
                                }
                                break;
                            #endregion

                            #region Unsupported conversions
                            default:
                                this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                this._conversionValidationMessage = "Conversion from " + DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                    + " to " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                    + " is not supported.";
                                break;
                            #endregion
                        }
                        break;
                        #endregion

                    case DataType.DT_CY:
                    case DataType.DT_DECIMAL:
                    case DataType.DT_FILETIME:
                    case DataType.DT_NUMERIC:
                        #region Decimal
                        switch (this._outDataType)
                        {
                            #region No conversion errors possible
                            case DataType.DT_BOOL:
                            case DataType.DT_CY:
                            case DataType.DT_DECIMAL:
                            case DataType.DT_FILETIME:
                            case DataType.DT_NUMERIC:
                                // no conversion errors possible
                                break;
                            #endregion

                            #region Warn about possible truncation (out-of-range)
                            case DataType.DT_I1:
                            case DataType.DT_UI1:
                            case DataType.DT_I2:
                            case DataType.DT_I4:
                            case DataType.DT_I8:
                            case DataType.DT_R4:
                            case DataType.DT_R8:
                            case DataType.DT_UI2:
                            case DataType.DT_UI4:
                            case DataType.DT_UI8:
                                if (this._errorRowDisposition == DTSRowDisposition.RD_FailComponent)
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " support a larger range of values than "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " - you may experience 'out of range' errors.";
                                }
                                break;
                            #endregion

                            #region Warn about possible string truncation
                            case DataType.DT_STR:
                            case DataType.DT_WSTR:
                                if ((this._truncationRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (this._inLength > this._outLength))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " is specified with " + this._inLength.ToString() + " digits.  Converting to "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " with a length of " + this._outLength.ToString() + " may result in truncation errors.";
                                }
                                break;
                            #endregion

                            #region Unsupported conversions
                            default:
                                this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                this._conversionValidationMessage = "Conversion from " + DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                    + " to " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                    + " is not supported.";
                                break;
                            #endregion
                        }
                        break;
                        #endregion

                    case DataType.DT_DBDATE:
                        #region Date
                        switch (this._outDataType)
                        {
                            #region No conversion errors possible
                            case DataType.DT_DBDATE:
                            case DataType.DT_DATE:
                            case DataType.DT_DBTIMESTAMP:
                                // no conversion errors possible
                                break;
                            #endregion

                            #region Detect string truncation
                            case DataType.DT_STR:
                            case DataType.DT_WSTR:
                                if ((this._truncationRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (this._inLength > STRINGFORMAT_DATE.Length))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " are converted to "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " using a '" + STRINGFORMAT_DATE + "' format.  The output column length of "
                                        + this._outLength.ToString() + " is insufficient.";
                                }
                                break;
                            #endregion

                            #region Unsupported conversions
                            default:
                                this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                this._conversionValidationMessage = "Conversion from " + DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                    + " to " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                    + " is not supported.";
                                break;
                            #endregion
                        }
                        break;
                        #endregion

                    case DataType.DT_DATE:
                    case DataType.DT_DBTIMESTAMP:
                        #region DateTime
                        switch (this._outDataType)
                        {
                            #region No conversion errors possible
                            case DataType.DT_DBDATE:
                            case DataType.DT_DATE:
                            case DataType.DT_DBTIMESTAMP:
                                // no conversion errors possible
                                break;
                            #endregion

                            #region Detect string truncation
                            case DataType.DT_STR:
                            case DataType.DT_WSTR:
                                if ((this._truncationRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (this._inLength > STRINGFORMAT_DATETIME.Length))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " are converted to "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " using a '" + STRINGFORMAT_DATETIME + "' format.  The output column length of "
                                        + this._outLength.ToString() + " is insufficient.";
                                }
                                break;
                            #endregion

                            #region Unsupported conversions
                            default:
                                this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                this._conversionValidationMessage = "Conversion from " + DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                    + " to " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                    + " is not supported.";
                                break;
                            #endregion
                        }
                        break;
                        #endregion

                    case DataType.DT_GUID:
                        #region GUID
                        switch (this._outDataType)
                        {
                            #region No conversion errors possible
                            case DataType.DT_GUID:
                                // no conversion errors possible
                                break;
                            #endregion

                            #region Detect string truncation
                            case DataType.DT_STR:
                            case DataType.DT_WSTR:
                                if ((this._truncationRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (this._inLength > Guid.NewGuid().ToString().Length))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " converted to "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " require " + Guid.NewGuid().ToString().Length.ToString() + " characters.  The output column length of "
                                        + this._outLength.ToString() + " is insufficient.";
                                }
                                break;
                            #endregion

                            #region Unsupported conversions
                            default:
                                this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                this._conversionValidationMessage = "Conversion from " + DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                    + " to " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                    + " is not supported.";
                                break;
                            #endregion
                        }
                        break;
                        #endregion

                    case DataType.DT_I1:
                    case DataType.DT_UI1:
                    case DataType.DT_I2:
                    case DataType.DT_I4:
                    case DataType.DT_I8:
                    case DataType.DT_UI2:
                    case DataType.DT_UI4:
                    case DataType.DT_UI8:
                        #region Integers
                        #region Find maximum # digits and bits on input
                        switch (this._inDataType)
                        {
                            case DataType.DT_I1:
                                maxInDigits = byte.MaxValue.ToString().Length;
                                maxInBits = 7;
                                break;
                            case DataType.DT_UI1:
                                maxInDigits = byte.MaxValue.ToString().Length;
                                maxInBits = 8;
                                break;
                            case DataType.DT_I2:
                                maxInDigits = Int16.MinValue.ToString().Length;
                                maxInBits = 15;
                                break;
                            case DataType.DT_UI2:
                                maxInDigits = UInt16.MaxValue.ToString().Length;
                                maxInBits = 16;
                                break;
                            case DataType.DT_I4:
                                maxInDigits = Int32.MinValue.ToString().Length;
                                maxInBits = 31;
                                break;
                            case DataType.DT_UI4:
                                maxInDigits = UInt32.MaxValue.ToString().Length;
                                maxInBits = 32;
                                break;
                            case DataType.DT_I8:
                                maxInDigits = Int64.MinValue.ToString().Length;
                                maxInBits = 63;
                                break;
                            case DataType.DT_UI8:
                                maxInDigits = UInt64.MaxValue.ToString().Length;
                                maxInBits = 64;
                                break;
                        }
                        #endregion
                        switch (this._outDataType)
                        {
                            #region No conversion errors possible
                            case DataType.DT_BOOL:
                            case DataType.DT_CY:
                            case DataType.DT_DECIMAL:
                            case DataType.DT_FILETIME:
                            case DataType.DT_NUMERIC:
                            case DataType.DT_R4:
                            case DataType.DT_R8:
                                // no conversion errors possible
                                break;
                            #endregion

                            #region Warn about possible truncation (out-of-range)
                            case DataType.DT_I1:
                            case DataType.DT_UI1:
                            case DataType.DT_I2:
                            case DataType.DT_UI2:
                            case DataType.DT_I4:
                            case DataType.DT_UI4:
                            case DataType.DT_I8:
                            case DataType.DT_UI8:
                                #region Find maximum # digits and bits on output
                                switch (this._outDataType)
                                {
                                    case DataType.DT_I1:
                                        maxOutDigits = byte.MaxValue.ToString().Length;
                                        maxOutBits = 7;
                                        break;
                                    case DataType.DT_UI1:
                                        maxOutDigits = byte.MaxValue.ToString().Length;
                                        maxOutBits = 8;
                                        break;
                                    case DataType.DT_I2:
                                        maxOutDigits = Int16.MinValue.ToString().Length;
                                        maxOutBits = 15;
                                        break;
                                    case DataType.DT_UI2:
                                        maxOutDigits = UInt16.MaxValue.ToString().Length;
                                        maxOutBits = 16;
                                        break;
                                    case DataType.DT_I4:
                                        maxOutDigits = Int32.MinValue.ToString().Length;
                                        maxOutBits = 31;
                                        break;
                                    case DataType.DT_UI4:
                                        maxOutDigits = UInt32.MaxValue.ToString().Length;
                                        maxOutBits = 32;
                                        break;
                                    case DataType.DT_I8:
                                        maxOutDigits = Int64.MinValue.ToString().Length;
                                        maxOutBits = 63;
                                        break;
                                    case DataType.DT_UI8:
                                        maxOutDigits = UInt64.MaxValue.ToString().Length;
                                        maxOutBits = 64;
                                        break;
                                }
                                #endregion
                                if ((this._errorRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (maxInBits > maxOutBits))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " support a larger range of values than "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " - you may experience 'out of range' truncation errors.";
                                }
                                break;
                            #endregion

                            #region Warn about possible string truncation
                            case DataType.DT_STR:
                            case DataType.DT_WSTR:
                                if ((this._truncationRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (maxInDigits > this._outLength))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " can represent values that occupy up to " + maxInDigits.ToString() + " digits.  Converting to "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " with a length of " + this._outLength.ToString() + " may result in truncation errors.";
                                }
                                break;
                            #endregion

                            #region Unsupported conversions
                            default:
                                this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                this._conversionValidationMessage = "Conversion from " + DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                    + " to " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                    + " is not supported.";
                                break;
                            #endregion
                        }
                        break;
                        #endregion

                    case DataType.DT_R4:
                    case DataType.DT_R8:
                        #region Real numbers
                        #region Find maximum # digits and bits on input
                        if (this._inDataType == DataType.DT_R4)
                        {
                            maxInBits = 32;
                        }
                        else
                        {
                            maxInBits = 64;
                        }
                        #endregion
                        switch (this._outDataType)
                        {
                            #region No conversion errors possible
                            case DataType.DT_BOOL:
                            case DataType.DT_CY:
                            case DataType.DT_DECIMAL:
                            case DataType.DT_FILETIME:
                            case DataType.DT_NUMERIC:
                            case DataType.DT_I1:
                            case DataType.DT_UI1:
                            case DataType.DT_I2:
                            case DataType.DT_UI2:
                                // no conversion errors possible
                                break;
                            #endregion

                            #region Warn about possible truncation (out-of-range)
                            case DataType.DT_I4:
                            case DataType.DT_UI4:
                            case DataType.DT_I8:
                            case DataType.DT_UI8:
                                #region Find maximum # digits and bits on output
                                switch (this._outDataType)
                                {
                                    case DataType.DT_I4:
                                        maxOutDigits = Int32.MinValue.ToString().Length;
                                        maxOutBits = 31;
                                        break;
                                    case DataType.DT_UI4:
                                        maxOutDigits = UInt32.MaxValue.ToString().Length;
                                        maxOutBits = 32;
                                        break;
                                    case DataType.DT_I8:
                                        maxOutDigits = Int64.MinValue.ToString().Length;
                                        maxOutBits = 63;
                                        break;
                                    case DataType.DT_UI8:
                                        maxOutDigits = UInt64.MaxValue.ToString().Length;
                                        maxOutBits = 64;
                                        break;
                                }
                                #endregion
                                if ((this._errorRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (maxInBits > maxOutBits))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " support a larger range of values than "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " - you may experience 'out of range' truncation errors.";
                                }
                                break;
                            #endregion

                            #region Warn about possible string truncation
                            case DataType.DT_STR:
                            case DataType.DT_WSTR:
                                if ((this._truncationRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (maxInDigits > this._outLength))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " can represent values that occupy up to " + maxInDigits.ToString() + " digits.  Converting to "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " with a length of " + this._outLength.ToString() + " may result in truncation errors.";
                                }
                                break;
                            #endregion

                            #region Unsupported conversions
                            default:
                                this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                this._conversionValidationMessage = "Conversion from " + DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                    + " to " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                    + " is not supported.";
                                break;
                            #endregion
                        }
                        break;
                        #endregion

                    case DataType.DT_STR:
                    case DataType.DT_WSTR:
                        #region String
                        switch (this._outDataType)
                        {
                            #region No conversion errors possible
                            case DataType.DT_BOOL:
                            case DataType.DT_CY:
                            case DataType.DT_DECIMAL:
                            case DataType.DT_FILETIME:
                            case DataType.DT_NUMERIC:
                                // no conversion errors possible
                                break;
                            #endregion

                            #region Warn about recommended source string format
                            case DataType.DT_DBDATE:
                            case DataType.DT_DATE:
                            case DataType.DT_DBTIMESTAMP:
                                if (this._errorRowDisposition == DTSRowDisposition.RD_FailComponent)
                                {
                                    string sourceFormat = STRINGFORMAT_DATETIME;
                                    if (this._inDataType == DataType.DT_DBDATE)
                                    {
                                        sourceFormat = STRINGFORMAT_DATE;
                                    }
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " should be formatted " + sourceFormat + " in order to assure an unambiguous conversion to "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName + ".";
                                }
                                break;
                            #endregion

                            #region Warn about possible truncation (out-of-range)
                            case DataType.DT_I1:
                            case DataType.DT_UI1:
                            case DataType.DT_I2:
                            case DataType.DT_I4:
                            case DataType.DT_I8:
                            case DataType.DT_R4:
                            case DataType.DT_R8:
                            case DataType.DT_UI2:
                            case DataType.DT_UI4:
                            case DataType.DT_UI8:
                                #region Find maximum number of digits
                                switch (this._outDataType)
                                {
                                    case DataType.DT_I1:
                                        maxOutDigits = byte.MaxValue.ToString().Length;
                                        break;
                                    case DataType.DT_UI1:
                                        maxOutDigits = byte.MaxValue.ToString().Length;
                                        break;
                                    case DataType.DT_I2:
                                        maxOutDigits = Int16.MinValue.ToString().Length;
                                        break;
                                    case DataType.DT_UI2:
                                        maxOutDigits = UInt16.MaxValue.ToString().Length;
                                        break;
                                    case DataType.DT_I4:
                                        maxOutDigits = Int32.MinValue.ToString().Length;
                                        break;
                                    case DataType.DT_UI4:
                                        maxOutDigits = UInt32.MaxValue.ToString().Length;
                                        break;
                                    case DataType.DT_I8:
                                        maxOutDigits = Int64.MinValue.ToString().Length;
                                        break;
                                    case DataType.DT_UI8:
                                        maxOutDigits = UInt64.MaxValue.ToString().Length;
                                        break;
                                    case DataType.DT_R4:
                                        maxOutDigits = Single.MaxValue.ToString().Length;
                                        break;
                                    case DataType.DT_R8:
                                        maxOutDigits = Double.MaxValue.ToString().Length;
                                        break;
                                }
                                #endregion
                                if ((this._errorRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (this._inLength >= maxOutDigits))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " can contain values with up to " + maxOutDigits.ToString() + " digits. "
                                        + "Converting a " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " with " + this._inLength.ToString() + " characters may result in an out of bounds error.";
                                }
                                break;
                            #endregion

                            #region Warn about possible string truncation
                            case DataType.DT_STR:
                            case DataType.DT_WSTR:
                                if ((this._truncationRowDisposition == DTSRowDisposition.RD_FailComponent)
                                    && (this._inLength > this._outLength))
                                {
                                    this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Warning;
                                    this._conversionValidationMessage = DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                        + " is specified with " + this._inLength.ToString() + " digits.  Converting to "
                                        + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                        + " with a length of " + this._outLength.ToString() + " may result in truncation errors.";
                                }
                                break;
                            #endregion

                            #region Unsupported conversions
                            default:
                                this._conversionValidationResult = ColumnConversionConfig.ConversionValidationResultType.Error;
                                this._conversionValidationMessage = "Conversion from " + DtsDataTypes.GetDataTypeDescriptionStruct(this._inDataType).GeneralName
                                    + " to " + DtsDataTypes.GetDataTypeDescriptionStruct(this._outDataType).GeneralName
                                    + " is not supported.";
                                break;
                            #endregion
                        }
                        break;
                        #endregion
                }
                #endregion
            }
        }
        #endregion

        #region IXmlSerializable Members
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            System.Xml.XmlReader safereader;
            using (safereader = reader.ReadSubtree())
            {
                while (safereader.ReadState != System.Xml.ReadState.EndOfFile)
                {
                    safereader.Read();
                    System.Xml.XmlNodeType type = safereader.MoveToContent();
                    if ((type == System.Xml.XmlNodeType.Element) && (safereader.LocalName == this.GetType().Name))
                    {
                        this._inLineageID = Convert.ToInt32(safereader["InLineageID"]);
                        this._columnName = safereader["InColumnName"];
                        this._inDataType = (DataType)Enum.Parse(typeof(DataType), safereader["InDataType"]);
                        this._inLength = Convert.ToInt32(safereader["InLength"]);
                        this._inCodePage = Convert.ToInt32(safereader["InCodePage"]);
                        this._inPrecision = Convert.ToInt32(safereader["InPrecision"]);
                        this._inScale = Convert.ToInt32(safereader["InScale"]);
                        if (safereader["WantToOutput"] != null)
                        {
                            this._wantToOutput = Convert.ToBoolean(safereader["WantToOutput"]);
                        }
                        this._wantToConvert = Convert.ToBoolean(safereader["WantToConvert"]);
                        this._outLineageID = Convert.ToInt32(safereader["OutLineageID"]);
                        this._outDataType = (DataType)Enum.Parse(typeof(DataType), safereader["OutDataType"]);
                        this._outLength = Convert.ToInt32(safereader["OutLength"]);
                        this._outCodePage = Convert.ToInt32(safereader["OutCodePage"]);
                        this._outPrecision = Convert.ToInt32(safereader["OutPrecision"]);
                        this._outScale = Convert.ToInt32(safereader["OutScale"]);
                        this._errorLineageID = Convert.ToInt32(safereader["ErrorLineageID"]);
                        if (safereader["ErrorRowDisposition"] != null)
                        {
                            this._errorRowDisposition = (DTSRowDisposition)Enum.Parse(typeof(DTSRowDisposition), safereader["ErrorRowDisposition"]);
                        }
                        if (safereader["TruncationRowDisposition"] != null)
                        {
                            this._truncationRowDisposition = (DTSRowDisposition)Enum.Parse(typeof(DTSRowDisposition), safereader["TruncationRowDisposition"]);
                        }
                    }
                }
            }
            reader.Read();

            this.Validate();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("InLineageID", this._inLineageID.ToString());
            writer.WriteAttributeString("InColumnName", this._columnName);
            writer.WriteAttributeString("InDataType", Enum.GetName(typeof(DataType), this._inDataType));
            writer.WriteAttributeString("InLength", this._inLength.ToString());
            writer.WriteAttributeString("InCodePage", this._inCodePage.ToString());
            writer.WriteAttributeString("InPrecision", this._inPrecision.ToString());
            writer.WriteAttributeString("InScale", this._inScale.ToString());
            writer.WriteAttributeString("WantToOutput", this._wantToOutput.ToString());
            writer.WriteAttributeString("WantToConvert", this._wantToConvert.ToString());
            writer.WriteAttributeString("OutLineageID", this._outLineageID.ToString());
            writer.WriteAttributeString("OutDataType", Enum.GetName(typeof(DataType), this._outDataType));
            writer.WriteAttributeString("OutLength", this._outLength.ToString());
            writer.WriteAttributeString("OutCodePage", this._outCodePage.ToString());
            writer.WriteAttributeString("OutPrecision", this._outPrecision.ToString());
            writer.WriteAttributeString("OutScale", this._outScale.ToString());
            writer.WriteAttributeString("ErrorLineageID", this._errorLineageID.ToString());
            writer.WriteAttributeString("ErrorRowDisposition", Enum.GetName(typeof(DTSRowDisposition), this._errorRowDisposition));
            writer.WriteAttributeString("TruncationRowDisposition", Enum.GetName(typeof(DTSRowDisposition), this._truncationRowDisposition));
        }
        #endregion

        #region ToString
        public override string ToString()
        {
            string toString = this._columnName + " (" + this._inLineageID + ") "
                + "as " + DtsDataTypes.SpecificDataTypeTechnicalDescription(this._inDataType, this._inLength, this._inCodePage, this._inPrecision, this._inScale) + " ";
            if (!this._wantToOutput)
            {
                toString += "ignored";
            }
            else if (!this._wantToConvert)
            {
                toString += "unconverted";
            }
            else
            {
                toString += "to " + DtsDataTypes.SpecificDataTypeTechnicalDescription(this._outDataType, this._outLength, this._outCodePage, this._outPrecision, this._outScale);
            }

            return toString;
        }
        #endregion
    }
    #endregion

    #region CLASS: ColumnConversionConfigList
    public class ColumnConversionConfigList : List<ColumnConversionConfig>
    {
        #region Element Retrieval
        #region By In Lineage/Name
        public ColumnConversionConfig FindColumnConfigByInput(int inLineageID)
        {
            return this.FindColumnConfigByInput(inLineageID, null);
        }

        public ColumnConversionConfig FindColumnConfigByInput(int inLineageID, string columnName)
        {
            ColumnConversionConfig columnConfig = null;
            ColumnConversionConfig columnConfigByName = null;

            foreach (ColumnConversionConfig possibleColumnConfig in this)
            {
                if (possibleColumnConfig.InLineageID == inLineageID)
                {
                    columnConfig = possibleColumnConfig;
                    break;
                }
                else if (possibleColumnConfig.ColumnName == columnName)
                {
                    columnConfigByName = possibleColumnConfig;
                }
            }

            if (columnConfig != null)
            {
                return columnConfig;
            }
            else
            {
                return columnConfigByName;
            }
        }
        #endregion

        #region By Output Lineage
        public ColumnConversionConfig FindColumnConfigByOutput(int outLineageID)
        {
            return this.FindColumnConfigByOutput(outLineageID, null);
        }

        public ColumnConversionConfig FindColumnConfigByOutput(int outLineageID, string columnName)
        {
            ColumnConversionConfig columnConfig = null;
            ColumnConversionConfig columnConfigByName = null;

            foreach (ColumnConversionConfig possibleColumnConfig in this)
            {
                if (possibleColumnConfig.OutLineageID == outLineageID)
                {
                    columnConfig = possibleColumnConfig;
                    break;
                }
                else if (possibleColumnConfig.ColumnName == columnName)
                {
                    columnConfigByName = possibleColumnConfig;
                }
            }

            if (columnConfig != null)
            {
                return columnConfig;
            }
            else
            {
                return columnConfigByName;
            }
        }
        #endregion

        #region By Error Ouput Lineage
        public ColumnConversionConfig FindColumnConfigByError(int errorLineageID)
        {
            return this.FindColumnConfigByError(errorLineageID, null);
        }

        public ColumnConversionConfig FindColumnConfigByError(int errorLineageID, string columnName)
        {
            ColumnConversionConfig columnConfig = null;
            ColumnConversionConfig columnConfigByName = null;

            foreach (ColumnConversionConfig possibleColumnConfig in this)
            {
                if (possibleColumnConfig.ErrorLineageID == errorLineageID)
                {
                    columnConfig = possibleColumnConfig;
                    break;
                }
                else if (possibleColumnConfig.ColumnName == columnName)
                {
                    columnConfigByName = possibleColumnConfig;
                }
            }

            if (columnConfig != null)
            {
                return columnConfig;
            }
            else
            {
                return columnConfigByName;
            }
        }
        #endregion
        #endregion

        #region Properties
        public int CountWantToOutput
        {
            get
            {
                int count = 0;
                foreach (ColumnConversionConfig column in this)
                {
                    if (column.WantToOutput)
                    {
                        count++;
                    }
                }
                return count;
            }
        }

        public int CountWantToConvert
        {
            get
            {
                int count = 0;
                foreach (ColumnConversionConfig column in this)
                {
                    if (column.WantToConvert)
                    {
                        count++;
                    }
                }
                return count;
            }
        }
        #endregion

        #region Equals
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }
            return this.Equals(obj as ColumnConversionConfigList);
        }

        public bool Equals(ColumnConversionConfigList otherList)
        {
            if (otherList == null)
            {
                return false;
            }
            if (otherList.Count != this.Count)
            {
                return false;
            }
            foreach (ColumnConversionConfig columnConfig in this)
            {
                ColumnConversionConfig otherColumnConfig = null;
                foreach (ColumnConversionConfig possibleOtherColumnConfig in otherList)
                {
                    if (possibleOtherColumnConfig.ColumnName == columnConfig.ColumnName)
                    {
                        otherColumnConfig = possibleOtherColumnConfig;
                        break;
                    }
                }
                if (otherColumnConfig == null)
                {
                    return false;
                }
                if ((otherColumnConfig.InLineageID != columnConfig.InLineageID)
                    || (otherColumnConfig.InDataType != columnConfig.InDataType)
                    || (otherColumnConfig.InLength != columnConfig.InLength)
                    || (otherColumnConfig.InCodePage != columnConfig.InCodePage)
                    || (otherColumnConfig.InPrecision != columnConfig.InPrecision)
                    || (otherColumnConfig.InScale != columnConfig.InScale)
                    || (otherColumnConfig.OutLineageID != columnConfig.OutLineageID)
                    || (otherColumnConfig.OutDataType != columnConfig.OutDataType)
                    || (otherColumnConfig.OutLength != columnConfig.OutLength)
                    || (otherColumnConfig.OutCodePage != columnConfig.OutCodePage)
                    || (otherColumnConfig.OutPrecision != columnConfig.OutPrecision)
                    || (otherColumnConfig.OutScale != columnConfig.OutScale)
                    || (otherColumnConfig.ErrorLineageID != columnConfig.ErrorLineageID)
                    || (otherColumnConfig.ErrorRowDisposition != columnConfig.ErrorRowDisposition)
                    || (otherColumnConfig.TruncationRowDisposition != columnConfig.TruncationRowDisposition))
                {
                    return false;
                }
            }
            return true;
        }
        #endregion

        #region GetHashCode
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        #endregion
    }
    #endregion
}
